/*
 * Jamus
 * Copyright (C) 2010 Jamus Team
 *
 * Jamus is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jamus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Jamus.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * Plugin.cpp
 * amarok plugin
 *  Created on: Dec 29, 2009
 *      Author: christian moellinger
 */
#include <QCoreApplication>
#include <QEventLoop>
#include <QtDBus>
#include <iostream>

#include "QSignalWrapper.h"
#include "Plugin.h"
#include "../../NoCurrentTrackException.h"
#include "../../InvalidPlaybackStateException.h"

using namespace std;

namespace backend {
namespace amarok {

log4cxx::LoggerPtr Plugin::logger(log4cxx::Logger::getLogger("backend.amarok.Plugin"));

const QString service = "org.kde.amarok";
QSignalWrapper *testerW;
QDBusInterface *interface;

Plugin::Plugin()
{
	testerW = new QSignalWrapper(this);
	interface = new QDBusInterface(service,"/Player","org.freedesktop.MediaPlayer");

	QDBusConnection connection = QDBusConnection::sessionBus();
	if (connection.connect("","","org.freedesktop.MediaPlayer","CapsChange",testerW,SLOT(CapsChange(int)))!=true)
		LOG4CXX_ERROR(logger, "Could not connect dbus signal \"CapsChange\"");
	if (connection.connect("","","org.freedesktop.MediaPlayer","TrackListChange",testerW,SLOT(TrackListChange(int)))!=true)
		LOG4CXX_ERROR(logger, "Could not connect dbus signal \"TrackListChange\"");
	//if (connection.connect("","","org.freedesktop.MediaPlayer","StatusChange",testerW,SLOT(StatusChange(int,int,int,int)))!=true)
	//	LOG4CXX_ERROR(logger, "Could not connect dbus signal \"StatusChange\"");
}

void Plugin::run()
{
	finished=false;
	//no loop needed
}

void Plugin::cancel()
{
	finished=true;
}

QDBusMessage Plugin::callMethod(const QString path, const QString interface, const QString method, const QList<QVariant> arguments=QList<QVariant>())
{
	//const QString path = _p;
	QDBusConnection connection = QDBusConnection::sessionBus();
	QDBusMessage message = QDBusMessage::createMethodCall(service,path, interface, method);
	message.setArguments(arguments);
	LOG4CXX_DEBUG(logger, "dbus call " << path.toStdString() << "/" << interface.toStdString() << "/" << method.toStdString());
	QDBusMessage ret = connection.call(message);
	LOG4CXX_DEBUG(logger, "return-message type: " << ret.type());
	//TODO: better exception handling
	if (ret.type() == QDBusMessage::ErrorMessage)
		cout << ret.errorMessage().toStdString() << endl;
	return ret;

}

boost::posix_time::time_duration Plugin::getElapsed()
{
	QDBusMessage retM =this->callMethod("/Player","org.freedesktop.MediaPlayer","PositionGet");
	QList<QVariant> ret = retM.arguments();
	int position = ret.first().toInt();
	if (position==0) //no track playing
		throw NoCurrentTrackException();

	return boost::posix_time::milliseconds(position);
}

void Plugin::seek(const boost::posix_time::time_duration& pos)
{
	QList<QVariant> args;
	args.append(QVariant(pos.total_seconds()));
	this->callMethod("/Player", "org.freedesktop.MediaPlayer", "PositionSet", args);
	this->sendPlaybackStatusChanged();
}

int Plugin::getCurrentTrack()
{
	QDBusMessage retM =this->callMethod("/TrackList","org.freedesktop.MediaPlayer","GetCurrentTrack");
	QList<QVariant> ret = retM.arguments();

	int currentTrack = ret.first().toInt();
	if (currentTrack==-1)
		throw NoCurrentTrackException();

	return currentTrack;
}

Plugin::PlaybackStatus Plugin::getPlaybackStatus()
{
	QDBusMessage retM =this->callMethod("/Player","org.freedesktop.MediaPlayer","GetCaps");
	QList<QVariant> ret = retM.arguments();
	int status = ret.first().toInt();

	bool can_go_next;
	bool can_go_prev;
	bool can_pause;
	bool can_play;
	bool can_seek;
	bool can_provide_metadata;
	bool can_has_tracklist;

	can_go_next = (status & 1)>0;
	can_go_prev = (status & 2)>0;
	can_pause = (status & 4)>0;
	can_play = (status & 8)>0;
	can_seek = (status & 16)>0;
	can_provide_metadata = (status & 32)>0;
	can_has_tracklist = (status & 64)>0;

	if (can_pause) //playing
		return PLAY;
	else if (can_play && can_seek)
		return PAUSE;
	else
		return STOP;
}

void Plugin::play()
{
	this->callMethod("/Player","org.freedesktop.MediaPlayer","Play");

	if (this->getPlaybackStatus()!=PLAY)	//if not successful
		throw NoCurrentTrackException();
}

void Plugin::play(int track)
{
	assert(track >= 0);

	int currentTrack = this->getCurrentTrack();
	int a;

	/* dirty workaround, but amarok doesn't provide a function to make this easier */
	int oldVolume=this->getVolume();
	this->setVolume(0);

	if (currentTrack < track)
		for (a = currentTrack; a != track; a++)
			this->nextTrack();
	if (currentTrack > track)
		for (a = currentTrack; a != track; a--)
			this->prevTrack();

	this->setVolume(oldVolume);
	this->callMethod("/Player","org.freedesktop.MediaPlayer","Play");
}

void Plugin::pause()
{
	//if (this->getPlaybackStatus()!=PLAY)
	//	throw InvalidPlaybackStateException();

	this->callMethod("/Player","org.freedesktop.MediaPlayer","Pause");

	//if (this->getPlaybackStatus()!=PAUSE)
	//	throw NoCurrentTrackException();
}

void Plugin::playPause()
{
	this->callMethod("/Player","org.freedesktop.MediaPlayer","PlayPause");

	/*Plugin::PlaybackStatus p = this->getPlaybackStatus();

	if ((p != PAUSE) && (p != PLAY))
			throw NoCurrentTrackException();*/
}

void Plugin::stop()
{
	this->callMethod("/Player","org.freedesktop.MediaPlayer","Stop");
}

void Plugin::nextTrack()
{
	if ((unsigned int)this->getCurrentTrack()<(this->tracklist.size()-1))
		this->callMethod("/Player","org.freedesktop.MediaPlayer","Next");
	else
		throw std::range_error("");


}

void Plugin::prevTrack()
{
	if (this->getCurrentTrack()!=0)
		this->callMethod("/Player","org.freedesktop.MediaPlayer","Prev");
	else throw std::range_error("");
}

int Plugin::getVolume()
{
	QDBusMessage retMessage = this->callMethod("/Player", "org.freedesktop.MediaPlayer", "VolumeGet");
	QList<QVariant> ret = retMessage.arguments();
	int Volume = ret.first().toInt();
	return Volume;
}

void Plugin::setVolume(int vol)
{
	if (vol<0||vol>100)
		throw std::range_error("");

	QList<QVariant> args;
	args.append(QVariant(vol));
	this->callMethod("/Player", "org.freedesktop.MediaPlayer", "VolumeSet", args);
}

const Plugin::TrackList& Plugin::getTrackList()
{
	this->tracklist.clear();
	QDBusMessage retMessage = this->callMethod("/TrackList", "org.freedesktop.MediaPlayer", "GetLength");
	QList<QVariant> ret = retMessage.arguments();
	int TrackList_Length = ret.first().toInt();
	int a, b;

	LOG4CXX_DEBUG(logger, "got tracklistlength: " << TrackList_Length);

	for (a = 0; a < TrackList_Length; a++) //fetch metadata for each track
	{
		Track new_track;
		QMap<QString, QVariant> ret;
		QList<QVariant> args;
		args.append(QVariant((int) a));
		QDBusReply<QMap<QString, QVariant> > argumentList = this->callMethod("/TrackList", "org.freedesktop.MediaPlayer", "GetMetadata", args);

		if (argumentList.isValid())
		{
			QMap<QString, QVariant> map = argumentList.value();
			QMap<QString, QVariant>::iterator i = map.begin();
			for (b = 0; b < map.count(); b++)
			{
				if (i.key().toStdString()=="artist")
					new_track.setArtist(i.value().toString().toStdString());
				else if (i.key().toStdString()=="title")
					new_track.setTitle(i.value().toString().toStdString());
				else if (i.key().toStdString()=="location")
					new_track.setFileName(i.value().toString().toStdString());
				else if (i.key().toStdString()=="album")
					new_track.setAlbum(i.value().toString().toStdString());
				i++;
			}
			tracklist.push_back(new_track);
		}
	}

	return tracklist;
}

const Plugin::Playlist& Plugin::getPlaylist()
{
	/*amarok plugin doesn't make a difference between playlist and tracklist */
	return this->getTrackList();
}

void Plugin::addToPlaylist(const string& fileName)
{
	QList<QVariant> args;
	QString filename = fileName.c_str();
	args.append(QVariant(filename));
	args.append(QVariant((bool)false));
	this->callMethod("/TrackList", "org.freedesktop.MediaPlayer", "AddTrack", args);

	//sync our tracklist ... maybe not necessary, because frontend calls this function itself when it needs the tracklist?
	this->getTrackList();
}

void Plugin::removeFromPlaylist(int index)
{
	throw logic_error("operation removeFromPlaylist not supported");
}

void Plugin::clearPlaylist()
{
	for (size_t a=0; a < this->tracklist.size(); a++)
	{
	QList<QVariant> args;
	args.append(QVariant((int)a));
	this->callMethod("/TrackList", "org.freedesktop.MediaPlayer", "DelTrack", args);
	}
	//sync our tracklist ... maybe not necessary, because frontend calls this function itself when it needs the tracklist?
	this->getTrackList();
}

Plugin::~Plugin()
{

}

}	//namespace backend
}	//namespace amarok

extern "C" backend::amarok::Plugin* create()
{
	return new backend::amarok::Plugin();
}

extern "C" void destroy(backend::amarok::Plugin* p)
{
	delete p;
}
